using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using BitMiracle.LibJpeg.Classic;

namespace MKDS_Course_Editor.GCN.THP
{
	class THP
	{
		public struct Thp
		{
			public ThpHeader Header;
			public ThpComponents Components;
			public ThpFrame[] Frames;
			public struct ThpFrame
			{
				public ThpFrameHeader Header;
				public byte[] ImageData;
				public ThpAudioFrame[] AudioFrames;
				public struct ThpAudioFrame
				{
					public ThpAudioFrameHeader Header;
					public byte[] AudioData1;
					public byte[] AudioData2;
				}
			}
			public struct ThpHeader
			{
				public string tag; //'THP\0'

				public UInt32 version; //0x00011000 = 1.1, 0x00010000 = 1.0
				public UInt32 maxBufferSize; //maximal buffer size needed for one complete frame (header + video + audio)
				public UInt32 maxAudioSamples; //!= 0 if sound is stored in file, maximal number of samples in one frame.
				//you can use this field to check if file contains audio.


				public float fps; //usually 29.something (=0x41efc28f) for ntsc
				public UInt32 numFrames; //number of frames in the thp file
				public UInt32 firstFrameSize; //size of first frame (header + video + audio)

				public UInt32 dataSize; //size of all frames (not counting the thp header structures)

				public UInt32 componentDataOffset; //ThpComponents stored here (see below)
				public UInt32 offsetsDataOffset; //?? if != 0, offset to table with offsets of all frames?

				public UInt32 firstFrameOffset; //offset to first frame's data
				public UInt32 lastFrameOffset; //offset to last frame's data
			};

			public struct ThpComponents
			{
				public UInt32 numComponents; //usually 1 or 2 (video or video + audio)

				//component type 0 is video, type 1 is audio,
				//type 0xff is "no component" (numComponent many entries
				//are != 0xff)
				public byte[] componentTypes; //16

				public object[] InfoObjects; //16
			};

			public struct ThpVideoInfo
			{
				public UInt32 width;
				public UInt32 height;
				public UInt32 unknown; //only for version 1.1 thp files
			};
			public struct ThpAudioInfo
			{
				public UInt32 numChannels;
				public UInt32 frequency;
				public UInt32 numSamples;
				public UInt32 numData; //only for version 1.1 - that many
				//audio blocks are stored after each video block
				//(for surround sound?)
			};

			public struct ThpFrameHeader
			{
				public UInt32 nextTotalSize; //total size of NEXT frame (frame header, video and audio)
				public UInt32 prevTotalSize; //total size of PREVIOUS frame
				public UInt32 imageSize; //size of image frame of THIS frame
				public UInt32 audioSize; //size of one audio frame of THIS frame) <- only if the file contains audio
			};


			public struct ThpAudioFrameHeader
			{
				//size 80 byte
				public UInt32 channelSize; //size of one channel in bytes
				//(audio frame size = sizeof(ThpAudioFrameHeader)
				// + ThpAudioInfo.numChannels*ThpAudioFrameHeader.channelSize)
				public UInt32 numSamples; //number of samples/channel

				//the following tables store 5.11 fixed point numbers
				public short[] table1; //[16] table for first channel
				public short[] table2; //[16] table for second channel (stored for one channel videos as well)

				//these are explained below
				public Int16 channel1Prev1;
				public Int16 channel1Prev2;
				public Int16 channel2Prev1;
				public Int16 channel2Prev2;
			};
		}
		public static Thp Read(string file)
		{
			Thp p = new Thp();
			EndianBinaryReader er = new EndianBinaryReader(File.OpenRead(file), Endianness.BigEndian);
			p.Header.tag = er.ReadString(Encoding.ASCII, 4);
			p.Header.version = er.ReadUInt32();
			float version = (p.Header.version == 0x00011000 ? 1.1f : 1.0f);
			p.Header.maxBufferSize = er.ReadUInt32();
			p.Header.maxAudioSamples = er.ReadUInt32();
			p.Header.fps = er.ReadSingle();
			p.Header.numFrames = er.ReadUInt32();
			p.Header.firstFrameSize = er.ReadUInt32();
			p.Header.dataSize = er.ReadUInt32();
			p.Header.componentDataOffset = er.ReadUInt32();
			p.Header.offsetsDataOffset = er.ReadUInt32();
			p.Header.firstFrameOffset = er.ReadUInt32();
			p.Header.lastFrameOffset = er.ReadUInt32();

			er.BaseStream.Position = p.Header.componentDataOffset;

			p.Components.numComponents = er.ReadUInt32();
			p.Components.componentTypes = er.ReadBytes(16);
			p.Components.InfoObjects = new object[16];
			for (int i = 0; i < 16; i++)
			{
				switch (p.Components.componentTypes[i])
				{
					case 0://Video
						Thp.ThpVideoInfo v = new Thp.ThpVideoInfo();
						v.width = er.ReadUInt32();
						v.height = er.ReadUInt32();
						if (version == 1.1f)
						{
							v.unknown = er.ReadUInt32();
						}
						p.Components.InfoObjects[i] = v;
						break;
					case 1://Audio
						Thp.ThpAudioInfo a = new Thp.ThpAudioInfo();
						a.numChannels = er.ReadUInt32();
						a.frequency = er.ReadUInt32();
						a.numSamples = er.ReadUInt32();
						if (version == 1.1f)
						{
							a.numData = er.ReadUInt32();
						}
						p.Components.InfoObjects[i] = a;
						break;
					case 0xFF://Nothing
						p.Components.InfoObjects[i] = null;
						break;
				}
			}
			//int _currFrameNr = -1;
			uint _nextFrameOffset = p.Header.firstFrameOffset;
			uint _nextFrameSize = p.Header.firstFrameSize;
			bool containsa = p.Header.maxAudioSamples != 0;
			p.Frames = new Thp.ThpFrame[p.Header.numFrames];
			//try
			//{
			for (int i = 0; i < p.Header.numFrames; i++)
			{
				er.BaseStream.Position = _nextFrameOffset;
				p.Frames[i] = new Thp.ThpFrame();
				p.Frames[i].Header.nextTotalSize = er.ReadUInt32();
				p.Frames[i].Header.prevTotalSize = er.ReadUInt32();
				p.Frames[i].Header.imageSize = er.ReadUInt32();
				if (containsa)
				{
					p.Frames[i].Header.audioSize = er.ReadUInt32();
				}
				try
				{
					p.Frames[i].ImageData = er.ReadBytes((int)p.Frames[i].Header.imageSize);
				}
				catch
				{

				}
				if (containsa)
				{
					//int nr = 0;
					//while (p.Components.InfoObjects[nr++] is Thp.ThpAudioInfo) ;
					Thp.ThpAudioInfo a = (Thp.ThpAudioInfo)p.Components.InfoObjects[1];
					p.Frames[i].AudioFrames = new Thp.ThpFrame.ThpAudioFrame[a.numData];
					for (int j = 0; j < a.numData; j++)
					{
						p.Frames[i].AudioFrames[j] = new Thp.ThpFrame.ThpAudioFrame();
						p.Frames[i].AudioFrames[j].Header.channelSize = er.ReadUInt32();
						p.Frames[i].AudioFrames[j].Header.numSamples = er.ReadUInt32();

						p.Frames[i].AudioFrames[j].Header.table1 = new short[16];
						p.Frames[i].AudioFrames[j].Header.table2 = new short[16];
						for (int k = 0; k < 16; k++)
						{
							p.Frames[i].AudioFrames[j].Header.table1[k] = er.ReadInt16();
						}
						for (int k = 0; k < 16; k++)
						{
							p.Frames[i].AudioFrames[j].Header.table2[k] = er.ReadInt16();
						}
						p.Frames[i].AudioFrames[j].Header.channel1Prev1 = er.ReadInt16();
						p.Frames[i].AudioFrames[j].Header.channel1Prev2 = er.ReadInt16();
						p.Frames[i].AudioFrames[j].Header.channel2Prev1 = er.ReadInt16();
						p.Frames[i].AudioFrames[j].Header.channel2Prev2 = er.ReadInt16();
						p.Frames[i].AudioFrames[j].AudioData1 = er.ReadBytes((int)p.Frames[i].AudioFrames[j].Header.channelSize);
						if (a.numChannels == 2)
						{
							p.Frames[i].AudioFrames[j].AudioData2 = er.ReadBytes((int)p.Frames[i].AudioFrames[j].Header.channelSize);
						}
					}
				}
				_nextFrameOffset += _nextFrameSize;
				_nextFrameSize = p.Frames[i].Header.nextTotalSize;
			}
			//}
			//catch { }
			p = DoData(p);
			er.Close();
			return p;
		}
		private static Thp DoData(Thp file)
		{
			for (int i = 0; i < file.Header.numFrames; i++)
			{
				Thp.ThpFrame f = file.Frames[i];
				/*if (i == 0)
				{
					File.Create(@"C:\testthp.bin").Close();
					File.WriteAllBytes(@"C:\testthp.bin", f.ImageData);
				}*/
				int start = -1;
				int end = -1;
				/*for (int j = 0; j < f.ImageData.Length; j++)
				{
					if (f.ImageData[j] == 0xff && f.ImageData[j + 1] == 0xda && start == -1)
					{
						start = j;
					}
					else if (f.ImageData[j] == 0xff && f.ImageData[j + 1] == 0xd9 && end == -1)
					{
						end = j;
						break;
					}
				}*/
				countRequiredSize(f.ImageData, f.ImageData.Length, ref start, ref end);
				List<byte> buf = new List<byte>();
				//bool started = false;
				for (int j = 0; j < f.ImageData.Length; j++)
				{
					buf.Add(f.ImageData[j]);
					if (f.ImageData[j] == 0xff && j > start && j < end) // && f.ImageData[j + 1] != 0xda && f.ImageData[j + 1] != 0xd9)
					{
						buf.Add(0);
					}
					/*if (f.ImageData[j] == 0xff && f.ImageData[j + 1] == 0xda && !started)
					{
						started = true;
					}
					else if (f.ImageData[j] == 0xff && f.ImageData[j + 1] == 0xd9 && started)
					{
						started = false;
					}*/
				}
				f.ImageData = buf.ToArray();
				/*if (i == 0)
				{
					File.Create(@"C:\testthp2.bin").Close();
					File.WriteAllBytes(@"C:\testthp2.bin", f.ImageData);
				}*/
				file.Frames[i] = f;
			}
			return file;
		}
		private static int countRequiredSize(byte[] data, int size, ref int start, ref int end)
		{
			start = 2 * size;
			int count = 0;

			int j;
			for (j = size - 1; data[j] == 0; --j)
				; //search end of data

			if (data[j] == 0xd9) //thp file
				end = j - 1;

			for (int i = 0; i < end; ++i)
			{
				if (data[i] == 0xff)
				{
					//if i == srcSize - 1, then this would normally overrun src - that's why 4 padding
					//bytes are included at the end of src
					if (data[i + 1] == 0xda && start == 2 * size)
						start = i;
					if (i > start)
						++count;
				}
			}
			return size + count;
		}
		public struct DecStruct
		{
			public byte[] currSrcByte;
			public UInt32 blockCount;
			public byte index;
			public byte shift;
		};

		private static void thpAudioInitialize(ref DecStruct s, byte[] srcStart, ref int pos)
		{
			s.currSrcByte = srcStart;
			s.blockCount = 2;
			s.index = (byte)((s.currSrcByte[pos] >> 4) & 0x7);
			s.shift = (byte)(s.currSrcByte[pos] & 0xf);
			pos++;
		}

		private static Int32 thpAudioGetNewSample(ref DecStruct s, ref int pos)
		{
			//the following if is executed all 14 calls
			//to thpAudioGetNewSample() (once for each
			//microblock) because mask & 0xf can contain
			//16 different values and starts with 2
			if ((s.blockCount & 0xf) == 0)
			{
				s.index = (byte)((s.currSrcByte[pos] >> 4) & 0x7);
				s.shift = (byte)(s.currSrcByte[pos] & 0xf);
				pos++;
				s.blockCount += 2;
			}

			int ret;
			if ((s.blockCount & 1) != 0)
			{
				int t = (byte)((s.currSrcByte[pos] << 28) & 0xf0000000);
				ret = t >> 28; //this has to be an arithmetic shift
				pos++;
			}
			else
			{
				int t = (byte)((s.currSrcByte[pos] << 24) & 0xf0000000);
				ret = t >> 28; //this has to be an arithmetic shift
			}

			++s.blockCount;
			return ret;
		}

		public static int thpAudioDecode(ref short[] destBuffer, Thp.ThpFrame.ThpAudioFrame frame, bool separateChannelsInOutput, bool isInputStereo)
		{

		  UInt32 channelInSize = frame.Header.channelSize;//aDWORD(head->channelSize);
		  UInt32 numSamples = frame.Header.numSamples;//aDWORD(head->numSamples);

		  byte[] srcChannel1 = frame.AudioData1;
		  byte[] srcChannel2 = frame.AudioData2;

		  short[] table1 = frame.Header.table1;
		  short[] table2 = frame.Header.table2;

		  short[] destChannel1 = new short[numSamples];
		  short[] destChannel2 = new short[numSamples];
		  UInt32 delta;

		  

		  DecStruct s = new DecStruct();
		  int pos = 0;
		  if (!isInputStereo)
		  {
			  //mono channel in input

			  thpAudioInitialize(ref s, srcChannel1, ref pos);

			  short prev1 = frame.Header.channel1Prev1;
			  short prev2 = frame.Header.channel1Prev2;

			  for (int i = 0; i < numSamples; ++i)
			  {
				  long res = thpAudioGetNewSample(ref s, ref pos);
				  res = ((res << s.shift) << 11); //convert to 53.11 fixedpoint

				  //these values are 53.11 fixed point numbers
				  long val1 = table1[2 * s.index];
				  long val2 = table1[2 * s.index + 1];

				  //convert to 48.16 fixed point
				  res = (val1 * prev1 + val2 * prev2 + res) << 5;

				  //rounding:
				  UInt16 decimalPlaces = (UInt16)(res & 0xffff);
				  if (decimalPlaces > 0x8000) //i.e. > 0.5
					  //round up
					  ++res;
				  else if (decimalPlaces == 0x8000) //i.e. == 0.5
					  if ((res & 0x10000) != 0)
						  //round up every other number
						  ++res;

				  //get nonfractional parts of number, clamp to [-32768, 32767]
				  int final = (int)(res >> 16);
				  if (final > 32767) final = 32767;
				  else if (final < -32768) final = -32768;

				  prev2 = prev1;
				  prev1 = (short)final;
				  destChannel1[i] = (short)final;
				  destChannel2[i] = (short)final;
				  //destChannel1 += delta;
				  //destChannel2 += delta;
			  }
		  }
		  else
		  {
			  //two channels in input - nearly the same as for one channel,
			  //so no comments here (different lines are marked with XXX)

			  thpAudioInitialize(ref s, srcChannel1, ref pos);

			  short prev1 = frame.Header.channel1Prev1;
			  short prev2 = frame.Header.channel1Prev2;

			  for (int i = 0; i < numSamples; ++i)
			  {
				  long res = thpAudioGetNewSample(ref s, ref pos);
				  res = ((res << s.shift) << 11); //convert to 53.11 fixedpoint

				  //these values are 53.11 fixed point numbers
				  long val1 = table1[2 * s.index];
				  long val2 = table1[2 * s.index + 1];

				  //convert to 48.16 fixed point
				  res = (val1 * prev1 + val2 * prev2 + res) << 5;

				  //rounding:
				  UInt16 decimalPlaces = (UInt16)(res & 0xffff);
				  if (decimalPlaces > 0x8000) //i.e. > 0.5
					  //round up
					  ++res;
				  else if (decimalPlaces == 0x8000) //i.e. == 0.5
					  if ((res & 0x10000) != 0)
						  //round up every other number
						  ++res;

				  //get nonfractional parts of number, clamp to [-32768, 32767]
				  int final = (int)(res >> 16);
				  if (final > 32767) final = 32767;
				  else if (final < -32768) final = -32768;

				  prev2 = prev1;
				  prev1 = (short)final;
				  destChannel1[i] = (short)final;
				  destChannel2[i] = (short)final;
				  //destChannel1 += delta;
				  //destChannel2 += delta;
			  }
			  pos = 0;
			  thpAudioInitialize(ref s, srcChannel2, ref pos);

			  prev1 = frame.Header.channel2Prev1;
			  prev2 = frame.Header.channel2Prev2;

			  for (int i = 0; i < numSamples; ++i)
			  {
				  long res = thpAudioGetNewSample(ref s, ref pos);
				  res = ((res << s.shift) << 11); //convert to 53.11 fixedpoint

				  //these values are 53.11 fixed point numbers
				  long val1 = table2[2 * s.index];
				  long val2 = table2[2 * s.index + 1];

				  //convert to 48.16 fixed point
				  res = (val1 * prev1 + val2 * prev2 + res) << 5;

				  //rounding:
				  UInt16 decimalPlaces = (UInt16)(res & 0xffff);
				  if (decimalPlaces > 0x8000) //i.e. > 0.5
					  //round up
					  ++res;
				  else if (decimalPlaces == 0x8000) //i.e. == 0.5
					  if ((res & 0x10000) != 0)
						  //round up every other number
						  ++res;

				  //get nonfractional parts of number, clamp to [-32768, 32767]
				  int final = (int)(res >> 16);
				  if (final > 32767) final = 32767;
				  else if (final < -32768) final = -32768;

				  prev2 = prev1;
				  prev1 = (short)final;
				  destChannel1[i] = (short)final;
				  destChannel2[i] = (short)final;
				  //destChannel1 += delta;
				  //destChannel2 += delta;
			  }
		  }
		  if (separateChannelsInOutput)
		  {
			  //separated channels in output
			  destBuffer = new short[numSamples * 2];
			  Array.Copy(destChannel1, destBuffer, numSamples);
			  Array.Copy(destChannel2,0, destBuffer, numSamples, numSamples);
			  delta = 1;
		  }
		  else
		  {
			  //interleaved channels in output
			  destBuffer = new short[numSamples * 2];
			  for (int i = 0; i < numSamples; i++)
			  {
				  destBuffer[i * 2] = destChannel1[i];
				  destBuffer[i * 2 + 1] = destChannel2[i];
			  }
			  delta = 2;
		  }


		  return (int)numSamples;
		}
		public static Bitmap GetFrame(Thp file, int frame)
		{
			Thp.ThpFrame f = file.Frames[frame];
			Thp.ThpVideoInfo a = (Thp.ThpVideoInfo)file.Components.InfoObjects[0];
			BitMiracle.LibJpeg.Jpeg j = new BitMiracle.LibJpeg.Jpeg();
			j.DecompressionParameters.DoFancyUpsampling = false;
			j.DecompressionParameters.DoBlockSmoothing = false;
			j.DecompressionParameters.EnableTwoPassQuantizer = false;
			j.DecompressionParameters.QuantizeColors = false;
			//j.DecompressionParameters.EnableExternalQuant = true;
			//j.DecompressionParameters.TwoPassQuantize = false;
			//j.DecompressionParameters.BufferedImage = true;
			//j.DecompressionParameters.DitherMode = BitMiracle.LibJpeg.DitherMode.Ordered;
			j.DecompressionParameters.DCTMethod = BitMiracle.LibJpeg.DCTMethod.Float;
			//j.DecompressionParameters.ScaleNumerator = 1;
			MemoryStream ou = new MemoryStream();
			j.Decompress(new MemoryStream(f.ImageData), new BitMiracle.LibJpeg.BitmapDestination(ou));
			return new Bitmap(ou);//new BitMiracle.LibJpeg.JpegImage(lines.ToArray(), BitMiracle.LibJpeg.Colorspace.RGB).ToBitmap();
		}
	}
}
